package main.graph;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;

import main.model.Entity;
import main.model.OntoEle;
import main.model.OntoNodePair;
import main.model.OntoPath;
import main.model.Relationship;
import main.model.Tree2OntoMapping;

import y.base.Edge;
import y.base.EdgeCursor;
import y.base.Node;
import y.base.NodeCursor;


/**
 * get the form trees from a formTrees.graph.mapping file
 * 
 * @author YuanAn
 *
 */
public class GetFormTreeWithMapping {
	
	
	/*
	 * get a set of form tree each of which is a ValuedGraph
	 */
	public static ArrayList<FormTreeWithMapping> getFormTrees(String fileName, ValuedGraph ontoGraph){
		
		ArrayList<FormTreeWithMapping> trees =  new ArrayList<FormTreeWithMapping>();
		
		try{
			BufferedReader in = new BufferedReader(new FileReader(new File(fileName)));
			
			FormTreeWithMapping formTree = null; //pointer to a form tree
			HashMap<String, Node> valueToNode = new HashMap<String, Node>(); //hold mapping from string value to node
			HashMap<String, Edge> valueToEdge = new HashMap<String, Edge>(); //hold mapping from string value to edge
			
			String line;
			
			int lineNum = 0; //counting the line numbers
			
			boolean startFormTree = false; 
			boolean startTreeNodes = false; 
			boolean startTreeEdges = false;
			boolean startNodes2OntoMap = false;
			boolean startEdges2OntoMap = false;
			
			while((line = in.readLine()) != null){
				
				lineNum++;
					
				String trimedLine = line.trim();
				
				if(trimedLine.equals("<FORMTREE>")){
					startFormTree = true;
					formTree = new FormTreeWithMapping();
					valueToNode.clear(); 
					valueToEdge.clear();
				}
				else if(trimedLine.equals("</FORMTREE>")){
					startFormTree = false;
					trees.add(formTree);
				}
				else if(trimedLine.equals("<tree nodes>"))
					startTreeNodes = true;
				else if(trimedLine.equals("</tree nodes>"))
					startTreeNodes = false;
				else if(trimedLine.equals("<tree edges>"))
					startTreeEdges = true;
				else if(trimedLine.equals("</tree edges>"))
					startTreeEdges = false;
				else if(trimedLine.equals("<treeNodes2onto mapping>"))
					startNodes2OntoMap = true;
				else if(trimedLine.equals("</treeNodes2onto mapping>"))
					startNodes2OntoMap = false;
				else if(trimedLine.equals("<treeEdges2onto mapping>"))
					startEdges2OntoMap = true;
				else if(trimedLine.equals("</treeEdges2onto mapping>"))
					startEdges2OntoMap = false;
				else if (startFormTree && !startTreeNodes && !startTreeEdges && !startNodes2OntoMap && !startEdges2OntoMap &&
						trimedLine.indexOf("formtree #") >= 0){
					String treeNum = getFormTreeNum(trimedLine);
					if(treeNum != null)
						formTree.setName(treeNum);
				}
				else if(startFormTree && startTreeNodes && !startTreeEdges && !startNodes2OntoMap && !startEdges2OntoMap){//create tree nodes
					 Node node =  formTree.createNode();
					 String value = getValueFromBracketedString(trimedLine);
					 formTree.putNodeValue(node, value);
					 valueToNode.put(value, node);
				}
				else if(startFormTree && !startTreeNodes && startTreeEdges && !startNodes2OntoMap && !startEdges2OntoMap){//create tree edges
					Node[] endNodes = getTwoEndNodes(trimedLine, valueToNode);
					if(endNodes[0] == null || endNodes[1] == null){
						//something is wrong with the string line; exit and print the line
						exitProgramWithError("Coulnd't find the node corresponding to its name in the following line:", lineNum, trimedLine);					
					}
					Edge e = formTree.createEdge(endNodes[0], endNodes[1]);
					formTree.putEdgeValue(e, trimedLine);
					valueToEdge.put(trimedLine, e);
					
				}
				else if(startFormTree && !startTreeNodes && !startTreeEdges && !startNodes2OntoMap && startEdges2OntoMap){
					//create the mapping
					Tree2OntoMapping mapping = formTree.getMapping();
					String[] mappingEles =  trimedLine.split(":=:");
					if(mappingEles.length != 2)
						exitProgramWithError("The mapping line can't be broken into two parts.", lineNum, trimedLine);
					
					if(valueToEdge.get(mappingEles[0].trim()) == null)
						exitProgramWithError("There is no such an edge in the form tree.", lineNum, trimedLine);
					
					Edge e = valueToEdge.get(mappingEles[0].trim());
					OntoPath path = new OntoPath();
					
					ArrayList<String> pathEleStrings = getPathEleStrings(mappingEles[1].trim());
					
					//for debugging
					//System.out.println(pathEleStrings);
					
					for(String pathEleString: pathEleStrings){
	
						
						OntoEle pathEle = getPathEleFromOntoGraph(pathEleString, ontoGraph, path);
						if(pathEle == null){
							exitProgramWithError("The " + pathEleString + " doesn't exist.", lineNum, trimedLine);
						}
						path.addPathNode(pathEle);
						
					}
						
					mapping.addEdge2Onto(e, path);
					mapping.addNode2Onto(e.source(), path.getFirst());
					mapping.addNode2Onto(e.target(), path.getLast());
					
					//find the two ends of the edge in the ontology graph and add a mapping 
					//from the edge to the two ends
					
					//test
					System.out.println(trimedLine);
					
					OntoEle firstPathEle = path.getFirst();
					OntoEle secondPathEle = path.getLast();
					
					OntoNodePair twoEnds = new OntoNodePair(firstPathEle, secondPathEle);
					twoEnds.setFirstNode(path.getFirstNode());
					twoEnds.setSecondNode(path.getLastNode());
					
					formTree.putEdgeToTwoNodes(e, twoEnds);
					
				}
				
			}
			
			
			in.close();
		}
		catch(Exception e){
			e.printStackTrace();
		}
		
		
		return trees;
	}
	
	/**
	 * The line in the mapping file has problems. Exit.
	 * @param message
	 * @param lineNum
	 * @param line
	 */
	public static void exitProgramWithError(String message, int lineNum, String line){
		System.out.println(message);
		System.out.println(lineNum + ":" + line);
		System.exit(1);
	}
	
	/**
	 * extract the form tree number
	 * 
	 * @param line
	 * @return
	 */
	public static String getFormTreeNum(String line){
		
		//System.out.println("getFormTreeNum: " + line);
		
		String[] items = line.split(":");
		
		if(items.length > 1)	
				
			return items[1].trim();
		
		return null;
		
	}
	
	/**
	 * get the value for a node
	 */
	public static String getValueFromBracketedString(String line){
		int ind1 = line.indexOf("[");
		int ind2 = line.indexOf("]");
		
		if(ind1 > ind2){
			System.out.println("Coulnd't find a node in the following line:");
			System.out.println(line);
			System.exit(1);
		}
		
		return line.substring(ind1 + 1, ind2);
	}
	
	
	
	/**
	 * get the two end nodes of the edge represented by the string line
	 * The first node is the starting node.
	 * 
	 * @return
	 */
	public static Node[] getTwoEndNodes(String line, HashMap<String, Node> valueToNode){
		
		Node[] ans = {null, null};
		
		ArrayList<String> tempValues = new ArrayList<String>();
		
		Stack stack = new Stack();
		
		StringBuffer buffer =  new StringBuffer();
		for(int i = 0; i < line.length(); i++){
			char c = line.charAt(i);
			
			if(!stack.empty())//if the stack is not empty, it is in the adding character mode.
				buffer.append(c);
			else if(c == '['){//empty stack? check whether we should start adding character
				stack.push(c);
				buffer.append(c);
			}
			
			if(c == ']'){
				if(stack.empty()){
					System.out.println("No matched square bracket.");
					return ans;
				}
				else{
					stack.pop();
					if(stack.empty()){
						tempValues.add(buffer.toString());
						buffer = new StringBuffer();
					}
				}
			}
		}
		
		if(tempValues.size() != 2){
			System.out.println("Can't find two ends.");
			return ans;
		}
		
		String sNodeValue = getValueFromBracketedString(tempValues.get(0).trim());
		Node sNode = valueToNode.get(sNodeValue);
		if (sNode == null)
			System.out.println("Source node is null.");
		ans[0] = sNode;
		
		String tNodeValue = getValueFromBracketedString(tempValues.get(1).trim());
		Node tNode = valueToNode.get(tNodeValue);
		if (tNode == null)
			System.out.println("Target node is null.");
		ans[1] = tNode;
		
		return ans;
	}
	
	public static ArrayList<String> getPathEleStrings(String line){
		
		ArrayList<String> ans = new ArrayList<String>();
		
		Stack stack = new Stack();
		
		StringBuffer buffer =  new StringBuffer();
		for(int i = 0; i < line.length(); i++){
			char c = line.charAt(i);
			
			if(!stack.empty())//if the stack is not empty, it is in the adding character mode.
				buffer.append(c);
			else if(c == '[' || c == '<'){//empty stack? check whether we should start adding character
				stack.push(c);
				buffer.append(c);
			}
			
			if(c == ']' || c == '>'){
				if(stack.empty()){
					System.out.println("No matched square bracket.");
					return null;
				}
				else{
					stack.pop();
					if(stack.empty()){
						ans.add(buffer.toString());
						buffer = new StringBuffer();
					}
				}
			}
		}
		
		return ans;
		
	}
	
	/**
	 * use the string name patheEleString to find a corresponding element in the ontology graph
	 * [xxxx] indicates an entity; <yyyy> indicates an attribute or relationship
	 * 
	 * @param pathEleString
	 * @param ontoGraph
	 * @param path //if the ontoEle is an attribute or relationship, the owner entity is the last
	 * element in the current path.
	 * @return
	 */
	public static OntoEle getPathEleFromOntoGraph(String pathEleString, ValuedGraph ontoGraph, OntoPath path){
		
		OntoEle ans = null;
		
		char firstC = pathEleString.charAt(0);
		char lastC = pathEleString.charAt(pathEleString.length() - 1);
		
	    if(!(firstC == '[' && lastC == ']') && !(firstC == '<' && lastC == '>'))
	    	return ans;
		
	    String eleString = pathEleString.substring(1, pathEleString.length() - 1);
	    
	    if(firstC == '[' && lastC == ']'){//find an entity
	    	
	    	//System.out.println("getPathEleFromOntoGraph: eleString: " + eleString);
	    	for(NodeCursor nc = ontoGraph.nodes(); nc.ok(); nc.next()){
	  		  Node node = nc.node();
	  		  Object v = ontoGraph.getNodeValue(node);
	  		  if(v instanceof Entity){
	  			  Entity entity = (Entity) v;
	  			  if(entity.getLabel().equalsIgnoreCase(eleString)){
	  				  ans = entity;
	  				  break;
	  			  }
	  		  }
	  	  }
	    }
	    else{//find an attribute or a relationship
	    	OntoEle owner = path.getLast();
	    	if(owner instanceof Entity){
	    		Node ownerNode = ((Entity) owner).getRefNode();
	    		for(EdgeCursor ec = ownerNode.edges(); ec.ok(); ec.next()){
	    			Edge e = ec.edge();
	    			Object v = ontoGraph.getEdgeValue(e);
	    			if(v instanceof Relationship){
	    				Relationship rel = (Relationship) v;
	    				//System.out.println("Relationship's name: " + rel.getName());
	    				if(rel.getName().equalsIgnoreCase(eleString)){
	    					
	    					//find an edge that matches a label in the path; add the edge
	    					//to the edgePath list of the mapping path
	    					path.addEdge(e);
	    					
	    					if(rel.getType() == Relationship.ASSOCIATION)
	    						ans = rel;
	    					else if(rel.getType() == Relationship.ATTRIBUTE)
	    						ans = rel.getTarget();
	    					break;
	    				}
	    			}
	    		}
	    	}
	    }
	    
		return ans;
		
	}
	
	//find the two ends of the edge in the ontology graph and add a mapping 
	//from the edge to the two ends
	public static OntoNodePair getTwoEndsInOntology(FormTreeWithMapping formTree, Edge e, ValuedGraph ontoGraph){
		
		Node firstTreeNode = e.source();
		Node secondTreeNode = e.target();
		
		String firstTreeNodeString = (String) formTree.getNodeValue(firstTreeNode); 
		String secondTreeNodeString = (String) formTree.getNodeValue(secondTreeNode);
		
		Node firstOntoNode = null, secondOntoNode = null;
		OntoEle firstOntoEle = null, secondOntoEle = null;
		
		ArrayList ontoGraphFirstEles = ontoGraph.getElementsByLabel(firstTreeNodeString);
		if(!ontoGraphFirstEles.isEmpty()){
			Object ele = ontoGraphFirstEles.get(0);
			if(ele instanceof Node)
				firstOntoNode = (Node)ele;
			else if(ele instanceof Edge)
				firstOntoNode = ((Edge)ele).source();
			
			firstOntoEle = (OntoEle) ontoGraph.getNodeValue(firstOntoNode);
		}
		
		ArrayList ontoGraphSecondEles = ontoGraph.getElementsByLabel(secondTreeNodeString);
		if(!ontoGraphSecondEles.isEmpty()){
			Object ele = ontoGraphSecondEles.get(0);
			if(ele instanceof Node)
				secondOntoNode = (Node)ele;
			else if(ele instanceof Edge)
				secondOntoNode = ((Edge)ele).target();
			
			secondOntoEle = (OntoEle) ontoGraph.getNodeValue(secondOntoNode);
		}
		
		
		OntoNodePair pair = new OntoNodePair(firstOntoEle, secondOntoEle);
		pair.setFirstNode(firstOntoNode);
		pair.setSecondNode(secondOntoNode);
		
		return pair;
	}
	
	/**
	 * display the set of form trees as string
	 * 
	 */
	public static String displayFormTrees(ArrayList<FormTreeWithMapping> trees, ValuedGraph ontoGraph){
		
		String ans = "";
		
		ans += "There are total: " + trees.size() + " form trees.\n\n";
		
		int total = 0;
		
		for(FormTreeWithMapping tree: trees){
			
			//count the total number of nodes and edges
			total += tree.N() + tree.E();
			
			ans += "<FORMTREE>\n";
			ans += "form tree #: " + tree.getName() + "\n";
			
			ans += "<tree nodes>\n";
			for(NodeCursor nc = tree.nodes(); nc.ok(); nc.next()){
				Node n = nc.node();
				String v = (String) tree.getNodeValue(n);
				ans += "[" + v + "]\n";
			}
			ans += "</tree nodes>\n";
			
			ans += "<tree edges>\n";
			for(EdgeCursor ec = tree.edges(); ec.ok(); ec.next()){
				Edge e = ec.edge();
				String v = (String)tree.getEdgeValue(e);
				ans += v + "\n";
			}
			ans += "</tree edges>\n";
			
			ans += tree.getMapping().display();
			ans += "\n";
			
			ans += "================Edges To OntoNodePairs====================\n";
			
			for(EdgeCursor ec = tree.edges(); ec.ok(); ec.next()){
				Edge e = ec.edge();
				OntoNodePair pair = tree.getTwoEnds(e);
				String v = (String)tree.getEdgeValue(e);
				ans += v + "<->" + pair.display() + "<->"; 
				
				OntoEle firstNodeEle = (OntoEle)ontoGraph.getNodeValue(pair.getFirstNode());
				OntoEle secondNodeEle = (OntoEle)ontoGraph.getNodeValue(pair.getSecondNode());
				
				//ans += "(" + pair.getFirstNode().toString() + "," + pair.getSecondNode().toString() +")\n";
				ans += "(" + firstNodeEle.getName() + "," + secondNodeEle.getName()  +")\n";
			}
			
		}
		
		//average size
		int avgSize = total / trees.size();
		
		ans += "The average size of tree is: " + avgSize + "\n";
		
		
		
		
		return ans;
		
	}

}
